/*
 * main.c
 * 
 * Producer-Consumer Problem
 *	
 * Authors:
 *		Mohamed Ali
 *		Donald Cheng
 *		Krzysztof Mazurek
 *		Ishan Sahay
 *	
 * Date: 24 / Oct / 2012
 *	Rev. 31 / Oct / 2012
 *	
 *
 */

#include <pthread.h> 
#include <stdio.h> 
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <sys/types.h>

#include "buffer.h"

#define TRUE 1
#define SLEEP_MAX 5

int complete = 0;
int p = 1;
int c = 1;

void *producer(void *param); /* producer thread */
void *consumer(void *param); /* consumer thread */

int main(int argc, char *argv[])
{
	printf("1. Get Command Line Arguments\n"); 
	
	//Checking for invalid input
	if (argc != 4 ) { 
		fprintf(stderr,"Invalid number of arguments\n"); 
		return -1;
	}

	if (atoi(argv[1]) <= 0 || atoi(argv[2]) <= 0 || atoi(argv[3]) <= 0) {
		fprintf(stderr,"%d, %d and %d must be > 0\n",atoi(argv[1]), atoi(argv[2]), atoi(argv[3]) ); 
		return -1;
	}
		

	/* initialize random seed: */
	//srand ( time(NULL) );

	/*Initial Test cases:
		int sleepTime = 15, producers = 1, consumers = 1;
	*/
	
	int sleepTime = atoi(argv[1]);
	int producers = atoi(argv[2]);
	int consumers = atoi(argv[3]);
	int i = 0;

	printf("2. Initialize mutex & semaphore(s)\n"); 
		
	/* create the mutex lock */ 
	pthread_mutex_init(&mutex,NULL);
	
	/* create the semaphore */ 
	sem_init(&empty, 0, BUFFER_SIZE-1);
	sem_init(&full, 0, 0);
	
	printf("3. Start Program\n");
	printf("4. Start Program\n");  
	
	pthread_t t_producer[producers], t_consumer[consumers]; /* the thread identifier */ 
	pthread_attr_t attr_producer[producers], attr_consumer[consumers]; /* set of thread attributes */
	
	printf("\nDue to printf() within producer(s)/consumer(s) are outside their respective critical section(s), the message of the item may appear delayed.\n\n");

//BUG: Index of producer(s)/consumer(s) is not passed to the thread(s)
	//Producer Threads
	for(i = 0; i < producers; i++){
		/* get the default attributes */ 		
		pthread_attr_init(&attr_producer[i]); 		
		/* create the thread */ 
		pthread_create(&t_producer[i], &attr_producer[i], producer, NULL); /* producer thread */
	}

	//Consumer Threads	
	for(i = 0; i < consumers; i++){
		/* get the default attributes */ 				
		pthread_attr_init(&attr_consumer[i]); 	
		/* create the thread */ 
		pthread_create(&t_consumer[i], &attr_consumer[i], consumer, NULL); /* consumer thread */
	}
	

	printf("5. Sleep in %d sec..\n", sleepTime);

	sleep(sleepTime);	 
	complete = 1;

//BUG: Should wait for thread(s) to exit. Does not exit 'gracefully' when an odd number of producers and consumers.
	int j,k;
	
	/* cancel threads */
	for(j = 0; j < producers; j++){	
		pthread_cancel(t_producer[j]);
	}
	for(k = 0; k < consumers; k++)
		pthread_cancel(t_consumer[k]);
	/* wait for the threads to exit */
	/*	for(j = 0; j < producers; j++){	
			pthread_join(t_producer[j], NULL);
		}
		for(k = 0; k < consumers; k++)
			pthread_join(t_consumer[k], NULL);
	*/
			
	/* 1. Get command line arguments argv[1],argv[2],argv[3] */
	/* 2. Initialize buffer */ 
	/* 3. Create producer thread(s) */ 
	/* 4. Create consumer thread(s) */ 
	/* 5. Sleep */ 
	/* 6. Exit */
	
	printf("6. Exit Program\n"); 
	return 0;
	
}

/* Operating Systems Concepts Essentials
 *	Outline of Producer and Consumer Threads	p.268
 */
void *producer(void *param) { 
	buffer_item item;
	int number = p;
	p++;

	/* initialize random seed: */
	//srand ( 1 ) ;

	while (complete == 0) { 	
		/* sleep for a random period of time */ 
		//Sleeps for random period less than SLEEP_MAX
		sleep(rand()%SLEEP_MAX); 
		
		/* generate a random number */ 
		item = rand() %100 ; 
		if (insert_item(item) == -1)
			printf("report error condition PRODUCER"); 
		else //BUG: Due to printf() outside the critical section(s), the printf() may appear delayed.
			printf("producer %d produced %d\n", number, item); 
	}
	pthread_exit(0);
}	

void *consumer(void *param) {
	buffer_item item;
	int number = c;
	c++;

	/* initialize random seed: */
	//srand ( 1 );
	
	while (complete == 0) { 
		/* sleep for a random period of time */ 
		//Sleeps for random period less than SLEEP_MAX
		sleep( rand() % SLEEP_MAX ); 

		if (remove_item(&item) == -1)
			printf("report error condition CONSUMER"); 
		else //BUG: Due to printf() outside the critical section(s), the printf() may appear delayed.
			printf("consumer %d consumed %d\n", number, item);
	}
	pthread_exit(0);
}

/* Operating Systems Concepts Essentials
*	Outlined in 6.10 and 6.11
*/
int insert_item(buffer_item item) { 
	/* insert item into buffer
	 return 0 if successful, otherwise 
	 return -1 indicating an error condition */
	
	int flag = -1;
	
	/* acquire the semaphore Empty */ 
	sem_wait(&empty);
	
	/*** critical section ***/
	
	/* acquire the mutex lock */
	pthread_mutex_lock(&mutex);
	
	/*** critical section ***/
	/*
	 buffer[(emptyIndex)% BUFFER_SIZE] = item;
	 emptyIndex++;
	 */
	//fullIndex+1 => emptyIndex+1 => emptyIndex
	buffer[ (emptyIndex) ] = item;
	emptyIndex = (emptyIndex+1) % BUFFER_SIZE;
	
	flag = 0;
	
	/* release the mutex lock */ 
	pthread_mutex_unlock(&mutex);
	
	/* release the semaphore Full */ 
	sem_post(&full);
		
	return flag;
}

int remove_item(buffer_item *item) { 
	/* remove an object from buffer
	 placing it in item 
	 return 0 if successful, otherwise 
	 return -1 indicating an error condition */
	
	int flag = -1;
	
	/* acquire the semaphore Full */ 
	sem_wait(&full);
	
	/*** critical section ***/
	
	/* acquire the mutex lock */ 
	pthread_mutex_lock(&mutex);
	
	/*** critical section ***/	
	//emptyIndex +1 => fullIndex+1 => fullIndex
	*item = buffer[(fullIndex)];
	buffer[fullIndex] = 0;
	fullIndex = (fullIndex+1) % BUFFER_SIZE;
	
	flag = 0;
	
	/* release the mutex lock */ 
	pthread_mutex_unlock(&mutex);
	
	/* release the semaphore Empty */ 
	sem_post(&empty);
	
	return flag;	
}
